package cn.edu.scut.hsrc.cluster.semantic;

import java.util.ArrayList;
import java.util.List;

import cn.edu.scut.hsrc.bean.Doc;
import cn.edu.scut.hsrc.bean.Pair;
import cn.edu.scut.hsrc.bean.Segment;
import cn.edu.scut.hsrc.bean.Word;
import cn.edu.scut.hsrc.bean.Word.RelationToQuery;
import cn.edu.scut.hsrc.bean.WordType.Type;
import cn.edu.scut.hsrc.util.SegUtil;
import cn.edu.scut.hsrc.util.StringUtil;

/**
 * 预处理类
 * <br>主要功能：
 * <br>1、对查询词进行分词和合并;
 * <br>2、统计查询词在文档标题中出现的比率;
 * <br>3、对文档和标题进行预处理，只保留和查询词有强关联的那部分;
 * 			
 * @file PreProcessor.java
 * @author feiyu
 *
 */

public class PreProcessor {
	
	private final String query;				//查询词
	private final Doc[] docs;				//待聚类的文档数据
	
	private List<Word> segedQuery;			//对第一词分词后查询词集合，通过计算原始聚类文档，发现常用短语组合（如百，慕，大，聚合成百慕大）
	
	private double queryInTitleRate;		//查询词在标题中出现的比率
	private boolean querySplited;			//查询词是否被切分标志位
	
	private List<Integer> errorDocIndex;	//分词出现问题的文档下标编号
	
	
	/**
	 * 
	 * 构造函数
	 * @param query		查询词
	 * @param docs		待聚类的文档
	 */
	public PreProcessor(String query,Doc[] docs) {
		// TODO Auto-generated constructor stub
		this.query = query.trim();
		this.docs = docs;
		this.segedQuery = new ArrayList<Word>();
		this.querySplited = true;
		this.errorDocIndex = new ArrayList<Integer>();
	}
	
	/**
	 * 
	 * 构造函数
	 * @param query		查询词
	 * @param docs		待聚类的文档
	 */
	public PreProcessor(String query,List<Doc> docs) {
		// TODO Auto-generated constructor stub
		this.query = query.trim();
		int size = docs.size();
		this.docs = new Doc[size];
		for(int i=0;i<size;i++)
			this.docs[i] = docs.get(i);
		this.segedQuery = new ArrayList<Word>();
		this.querySplited = true;
		this.errorDocIndex = new ArrayList<Integer>();
	}
	
	/**
	 * 做预处理
	 */
	public void doPreProcess()
	{
		//对查询词进行分词，并且合并潜在的词语
		segQuery();
		//计算查询词在标题中出现的比率
		calQueryInTitleRate();
		//对查询词和摘要进行预处理
		trunkTitleAndSummary();
	}
	
	/**
	 * 对查询词做第一次分词，返回第一次分词后的结果
	 * @return
	 */
	private void segQuery()
	{
		//第一步
		//对查询词进行分词
		List<Word> tmpQuerys = new ArrayList<Word>();
		if(this.query == null || this.query.equals(""))
			return;
		//调用分词器进行分词，返回分词后的字符串
		List<Pair<String, Type>> words = SegUtil.seg(this.query);
		int size = words.size();
		for(int i=0;i<size;i++)
		{
			Pair<String, Type> pair = words.get(i);
			//保留非标点符号的那部分
			if(pair.getSecond() != Type.BIAODIAN)
			{
				//这里和查询词关系用RelationToQuery.EQUAL，因为他本身就是查询词的一部分
				tmpQuerys.add(new Word(pair.getFirst(),pair.getSecond(),RelationToQuery.EQUAL));
			}
		}
		if(tmpQuerys.size() <= 1)
			this.querySplited = false;
		else this.querySplited = true;
		
		//输出第一步的结果
		/*System.out.print("第一次分词后的结果：");
		for (Word word : tmpQuerys) {
			System.out.print(word.getText()+",");
		}
		System.out.println();*/
		
		//第二步
		//对 分词后的查询词进行合并，查找潜在的可能作为一个短语的查询词组合，如"百","慕","大"，合并为"百慕大"
		//如果只是一个词，直接添加，然后返回
		int length = tmpQuerys.size();
		if(length == 0)
			return;
		if(length == 1)
		{
			this.segedQuery.add(tmpQuerys.get(0));
			return;
		}
		//开始合并词
		int currIndex = 1;
		Word preWord = tmpQuerys.get(currIndex-1);
		Word currWord = tmpQuerys.get(currIndex);
		
		while (currIndex < length ) {
			while((currIndex < length-1) && preWord.length() >= 2 && currWord.length() >=2 )
			{
				this.segedQuery.add(preWord);
				preWord = currWord;
				currWord = tmpQuerys.get(++currIndex);
			}
			//这里应该可以优化，如果两者是都是一个字或者有一个是一个字呢，是不是也可以合并？
			if(currIndex == length -1)
			{
				this.segedQuery.add(preWord);
				this.segedQuery.add(currWord);
				break;
			}
			//判断前后两个词是否可以合并，如果可以就合并
			if(canMerged(preWord.getText(), currWord.getText())){
				//词性用后一个词的词性
				preWord = new Word(preWord.getText()+currWord.getText(),currWord.getType(),currWord.getRelationToQuery());
				currWord = tmpQuerys.get(++currIndex);
			}else {
				this.segedQuery.add(preWord);
				preWord = currWord;
				currWord = tmpQuerys.get(++currIndex);
			}
		}
		
		//输出第二步的结果
		/*System.out.print("合并后的结果：");
		for (Word word : this.segedQuery) {
			System.out.print(word.getText()+",");
		}
		System.out.println();*/
	}
	
	/**
	 * 计算查询词在标题中出现的比率
	 */
	private void calQueryInTitleRate()
	{
		int all = 0;		//所有出现的次数（包括连贯和不连贯的情况）
		int linkedCount = 0;//连贯的情况
		int size = this.docs.length;
		for(int i=0;i<size;i++)
		{
			Doc doc = this.docs[i];
			int score = getScoreToQuery(doc.getTitle());
			//这里实现有点不一样
			if(score >= 1)
			{
				all++;
				if(score == 2)
					linkedCount++;
			}
		}
		if(this.querySplited && all > 0)
		{
			if((double)linkedCount/all >= 0.8)
				this.querySplited = false;
			else this.querySplited = true;
		}
		if(all > 0)
			this.queryInTitleRate = (double)all/size;
		else this.queryInTitleRate = 0;
	}
	
	/**
	 * 计算查询词在字符串string中的得分（次数）
	 * @param string 待计算的字符串
	 * @return 如果包含的是完整的query，就返回2，如果包含query分词后的词集中的一个，就返回1，否则返回0
	 */
	private int getScoreToQuery(String string)
	{
		int value = 0;
		if(string.indexOf(this.query) >= 0)
			value = 2;
		else {
			//这里用的是合并的查询词集合，有点不同
			int size = this.segedQuery.size();
			for(int i=0;i<size;i++)
			{
				String term = this.segedQuery.get(i).getText();
				if(term.length() == 1)
				{
					if(i < (size-1))
					{
						String term1 = this.segedQuery.get(i+1).getText();
						if(string.indexOf(term+term1) >= 0)
						{	
							value = 1;
							break;
						}
					}
				}else if(term.length() >= 2){
					if(string.indexOf(term) >= 0)
					{
						value = 1;
						break;
					}
				}
			}
		}
		return value ;
	}
	
	/**
	 * 对标题和摘要进行预处理
	 */
	private void trunkTitleAndSummary()
	{
		int size = this.docs.length;
		for(int i=0;i<size;i++)
		{
			try {
				this.docs[i].setTrunkedTitle(trunkTitle(this.docs[i].getTitle()));
				this.docs[i].setTrunkedSummaray(trunkSummary(this.docs[i].getSummary()));
			} catch (Exception e) {
				// TODO: handle exception
				/*e.printStackTrace();
				System.out.println(i);
				System.out.println(this.docs[i].getTitle());
				System.out.println(this.docs[i].getSummary());*/
				this.errorDocIndex.add(i);
			}
		}
	}
	
	/**
	 * 对摘要进行预处理,抽取出和查询词有强关联关系的部分
	 * @param summary
	 * @return 返回预处理后的摘要
	 */
	private List<Segment> trunkSummary(String summary)
	{
		int currIndex = 0;
		List<Segment> results = new ArrayList<Segment>();
		List<String> list = StringUtil.splitByPos(summary);
		int size = list.size();
		for(int i=0;i<size;i++)
		{
		//	if (getScoreToQuery(list.get(i)) >= 1) {//tian.yuchen
				results.add(new Segment(currIndex++, list.get(i)));
		//	}
		}
		return results;
	}
	
	/**
	 * 对标题进行预处理
	 * @param title
	 * @return 返回预处理后的标题
	 */
	private List<Segment> trunkTitle(String title)
	{
		List<Segment> results = new ArrayList<Segment>();
		if(title.indexOf(this.query) >= 0)
		{	//如果标题中包含完整的查询词，那就保存那些包含动名词的段和查询词的段
			//这里实现有点不同
			List<String> list = StringUtil.splitByPos(title);
			int size = list.size();
			int currIndex = 0;
			for(int i=0;i<size;i++)
			{
				if(getScoreToQuery(list.get(i)) >= 1)
				{
					results.add(new Segment(currIndex++, list.get(i)));
				}
				else {
					List<Pair<String, Type>> words = SegUtil.seg(list.get(i));
					boolean flag = false;
					for (Pair<String, Type> pair : words) 
					{
						if(pair.getSecond() == Type.NOUN || pair.getSecond() == Type.VERB)
						{
							flag = true;
							break;
						}
					}
					//
					//tian.yuchen
					//if(flag == true)
					if(true)
					{
						results.add(new Segment(currIndex++, list.get(i)));
					}
				}
			}
		}
		else {
			//如果标题中不包含完整的查询词
			int currIndex = 0;
			List<String> list = StringUtil.splitByPos(title);
			int size = list.size();
			for(int i=0;i<size;i++)
			{
				if (getScoreToQuery(list.get(i)) >= 1) {
					results.add(new Segment(currIndex++, list.get(i)));
				}
			}
			//消除标题是"    ---噪音"的情况
			if (results.size() == 0)
			{
				String tmp = StringUtil.getPreString(title, "-","_","--","——","-");
				List<String> tmpList = StringUtil.splitByPos(tmp);
				int tmpSize = tmpList.size();
				for(int i=0;i<tmpSize;i++)
					results.add(new Segment(currIndex++, tmpList.get(i)));
			}
		}
		return results;
	}
	
	
	/**
	 * 判断前后两个词是否可以合并，通过对所有文档进行遍历，计算他们协同出现的概率
	 * @param first
	 * @param second
	 * @return true 表示可以合并，false 表示不可以合并
	 */
	private boolean canMerged(String first,String second)
	{
		boolean result = false;
		String term = first+second;
		int fNum = 0;			//前面一个字符串出现的次数
		int sNum = 0;			//后面一个字符串出现的次数
		int togetherNum = 0;	//共同出现的次数
		int size = this.docs.length;
		for(int i=0;i<size;i++)
		{
			String title = this.docs[i].getTitle();
			String summary = this.docs[i].getSummary();
			if(title.indexOf(term) >=0 )
			{
				fNum++;
				sNum++;
				togetherNum++;
			}else
			{//这里实现有点不同
				if(title.indexOf(first) >= 0)
					fNum++;
				if(title.indexOf(second) >= 0)
					sNum++;
			}
			if(summary.indexOf(term) >=0 )
			{
				fNum++;
				sNum++;
				togetherNum++;
			}else
			{
				if(summary.indexOf(first) >= 0)
					fNum++;
				if(summary.indexOf(second) >= 0)
					sNum++;
			}
		}
		if(togetherNum >= 10)//这个数字值得商榷
		{
			int min = Math.min(fNum, sNum);
			if((double)min/togetherNum >= 0.9)//这个阈值也值得商榷
				result = true;
		}
		return result;
	}
	
	/**
	 * 获取查询词是否被切分
	 * @return true 表示被切分，false 表示没被切分
	 */
	public boolean isQuerySplited()
	{
		return this.querySplited;
	}

	/**
	 * 获取查询词在标题中出现的比率
	 * @return
	 */
	public double getQueryInTitleRate() {
		return this.queryInTitleRate;
	}
	
	/**
	 * 返回分词后的查询词集合
	 * @return
	 */
	public List<Word> getSegedQuery() {
		return this.segedQuery;
	}
	
	/**
	 * 返回预处理后的文档，注意：这里剔除了那些分词引起异常的文档
	 * @return
	 */
	public Doc[] getDocs() {
		if(this.errorDocIndex.size() == 0)
			return this.docs;
		int size = this.docs.length - this.errorDocIndex.size();
		Doc [] newDocs = new Doc[size];
		int allSize = this.docs.length;
		int currIndex = 0;
		for(int i=0;i<allSize;i++)
		{
			if(!this.errorDocIndex.contains(i))
			{
				newDocs[currIndex] = this.docs[i];
				currIndex++;
			}
		}
		return newDocs;
	}
}